package cn.flying.cloud.base.common.utils.crypto;

import javax.crypto.Cipher;

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * RSA 非对称加密工具类。
 * 1、公钥（PUBLIC_KEY）、私钥：PRIVATE_KEY 必须分开使用，比如公钥加密时，必须是私钥解密，反之私钥加密时，必须是公钥解密
 *
 * @author: admin
 * @date: 2025年01月23日 10:08
 * @version: 1.0
 */
public class RSAUtil {
    private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);

    /**
     * ALGORITHM_PKCS5PADDING : cipher 实例化时的 加密算法/反馈模式/填充方案。ECB 表示无向量模式
     * ALGORITHM: 创建密钥时使用的算法
     * KEY_PAIR_LENGTH: 秘钥对长度。数值越大，能加密的内容就越大。
     * <p>
     * 如 KEY_PAIR_LENGTH 为 1024 时加密数据的长度不能超过 117 字节
     * 如 KEY_PAIR_LENGTH 为 2048 时加密数据的长度不能超过 245 字节
     * 依次类推
     * </p>
     */
    private static final String ALGORITHM = "RSA";
    private static final String ALGORITHM_PKCS5PADDING = "RSA/ECB/PKCS1Padding";
    private static final int KEY_PAIR_LENGTH = 2048;
    public static final String PUBLIC_KEY = "publicKey";

    public static final String PRIVATE_KEY = "privateKey";

    /**
     * 生成 RSA 密钥对：公钥（PUBLIC_KEY）、私钥：PRIVATE_KEY
     *
     * @param keySeed 随机数生成器的种子，只要种子相同，则生成的公钥、私钥就是同一对. randomSeed 长度可以自定义，加/解密必须是同一个.
     */
    public static Map<String, String> generateKeyPair(String keySeed) {
        //KeyPair 是密钥对（公钥和私钥）的简单持有者。加密、解密都需要使用.
        try {
            //获取生成 RSA 加密算法的公钥/私钥对 KeyPairGenerator 对象
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
            //获取实现指定算法（SHA1PRNG）的随机数生成器（RNG）对象.
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            //重新设定此随机对象的种子.
            random.setSeed(keySeed.getBytes(StandardCharsets.UTF_8));
            /*
             * initialize(int keySize, SecureRandom random):使用给定的随机源（random）初始化特定密钥大小的密钥对生成器。
             * keySize: 健的大小值，这是一个特定于算法的度量。值越大，能加密的内容就越多，否则会抛异常：javax.crypto.IllegalBlockSizeException: Data must not be longer than xxx bytes
             * 如 keySize 为 2048 时加密数据的长度不能超过 245 字节。
             */
            keyPairGenerator.initialize(KEY_PAIR_LENGTH, random);
            KeyPair keyPair = keyPairGenerator.genKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            Map<String, String> keys = new HashMap<>();
            keys.put(PUBLIC_KEY, Base64.getEncoder().encodeToString(publicKey.getEncoded()));
            keys.put(PRIVATE_KEY, Base64.getEncoder().encodeToString(privateKey.getEncoded()));
            return keys;
        } catch (NoSuchAlgorithmException e) {
            logger.error("RSA公私钥生成失败", e);
            return new HashMap<>();
        }
    }

    /**
     * 加密
     *
     * @param data
     * @param pub
     * @return
     */
    public static String encrypt(String data, String pub) {
        try {
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            PublicKey publicKey = generatePublicKey(pub);
            Cipher cipher = Cipher.getInstance(ALGORITHM_PKCS5PADDING);
            //init(opMode, key)：初始化 Cipher. 1 表示加密模式（Cipher.ENCRYPT_MODE），2 表示解密模式（Cipher.DECRYPT_MODE）
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            /*执行加密 或 解密操作。如果 dataBytes 内容过长，则 doFinal 可能会抛出异常.
             *javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 dataBytes ：数据不能超过xxx字节
             * 此时需要调大 KEY_PAIR_LENGTH 的值*/
            byte[] bytes = cipher.doFinal(dataBytes);
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            logger.error("加密失败", e);
            return "";
        }
    }

    /**
     * 解密
     *
     * @param data
     * @param pri
     * @return
     */
    public static String decrypt(String data, String pri) {
        try {
            byte[] dataBytes = Base64.getDecoder().decode(data);
            PrivateKey privateKey = generatePrivateKey(pri);
            Cipher cipher = Cipher.getInstance(ALGORITHM_PKCS5PADDING);
            //init(opMode, key)：初始化 Cipher. 1 表示加密模式（Cipher.ENCRYPT_MODE），2 表示解密模式（Cipher.DECRYPT_MODE）
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            /*执行加密 或 解密操作。如果 contentBytes 内容过长，则 doFinal 可能会抛出异常.
             *javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 dataBytes ：数据不能超过xxx字节
             * 此时需要调大 KEY_PAIR_LENGTH 的值*/
            byte[] bytes = cipher.doFinal(dataBytes);
            return new String(bytes);
        } catch (Exception e) {
            logger.error("解密失败", e);
            return "";
        }
    }

    /**
     * 私钥签名 返回Base64编码
     *
     * @param data
     * @param pri
     * @return
     * @throws Exception
     */
    public static String sign(String data, String pri) throws Exception {
        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
        PrivateKey privateKey = generatePrivateKey(pri);
        //这个根据需求填充SHA1WithRSA或SHA256WithRSA
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initSign(privateKey);
        signature.update(bytes);
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 公钥验签
     *
     * @param data
     * @param sign
     * @param pub
     * @return
     */
    public static boolean verify(String data, String sign, String pub) {
        try {
            byte[] signBytes = Base64.getDecoder().decode(sign);
            PublicKey publicKey = generatePublicKey(pub);
            //这个根据需求填充SHA1WithRSA或SHA256WithRSA
            Signature signature = Signature.getInstance("SHA256WithRSA");
            signature.initVerify(publicKey);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            return signature.verify(signBytes);
        } catch (Exception e) {
            logger.error("验签失败--->", e);
            return false;
        }
    }

    /**
     * 将Base64编码的公钥串，转化为公钥对象
     *
     * @param pub
     * @return
     */
    public static PublicKey generatePublicKey(String pub) {
        try {
            byte[] decode = Base64.getDecoder().decode(pub);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decode);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            return keyFactory.generatePublic(publicKeySpec);
        } catch (Exception e) {
            logger.error("将Base64转码的公钥串，转化为公钥对象异常：{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 将Base64编码的私钥串，转化为私钥对象
     *
     * @param pri
     * @return
     */
    public static PrivateKey generatePrivateKey(String pri) {
        try {
            byte[] decode = Base64.getDecoder().decode(pri);
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decode);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception e) {
            logger.error("将Base64转码的私钥串，转化为私钥对象异常：{}", e.getMessage(), e);
            return null;
        }
    }

    public static void main(String[] args) {
        String content = "4524352345";
        String keySeed = "TPCz0lDvTHybGMsHTJi3mJ7Pt48llJmRHb";

        System.out.println("被加密数据字节大小：" + content.getBytes().length + " 字节，" + content.length() + " 个字符");
        System.out.println("源内容：\n" + content);
        try {
            //公钥、私钥必须分开使用，公钥解密时，必须是私钥解密，反之亦然.
            Map<String, String> pair = generateKeyPair(keySeed);
            System.out.println("公钥：" + pair.get(PUBLIC_KEY));
            System.out.println("私钥：" + pair.get(PRIVATE_KEY));
            String encrypted = encrypt(content, pair.get(PUBLIC_KEY));
            String decrypted = decrypt(encrypted, pair.get(PRIVATE_KEY));
            System.out.println("加密后：" + encrypted);
            System.out.println("解密后：" + decrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
